//################################################################################
// MIT License
// Copyright (c) 2024 ZhangYihua
//
// Change Logs:
// Date           Author       Notes
// 2020-11-01     ZhangYihua   first version
//
// Description  : 
//################################################################################

module lpsg_prbs #(      // low-pass signal generator by means of PRBS
parameter           LPSG_DW                 = 12,
parameter           LVL_BW                  = 4,
parameter           PRBS_N                  = 23,
parameter           EMA_K_MAX               = 15,   // Maximum of cfg_ema_k
parameter           EMA_K_MIN               = 8,    // Minimum of cfg_ema_k
parameter           GAIN_DW                 = 8,    // total bit-width
parameter           GAIN_FW                 = 5,    // fractional bit-width

// the following parameters are calculated automatically
parameter           EMA_KW                  = $clog2(EMA_K_MAX+1)     // cfg_ema_k bit_width
) ( 
input                                       rst_n,
input                                       clk,
input                                       cke,

output      signed  [LPSG_DW-1:0]           lpsg_dat,

input               [EMA_KW-1:0]            cfg_ema_k,
input               [GAIN_DW-1:0]           cfg_gain    // u(GAIN_DW, GAIN_FW)
);

//################################################################################
// define local varialbe and localparam
//################################################################################
localparam          PRBS_DW                 = MAX_F(PRBS_N, LVL_BW);
localparam          CIC_N                   = 5;    // -13.4dB * 5 = -67dB
localparam          CIC_R                   = 1;
localparam          CIC_M                   = 16;
localparam          CIC_ACC_DW              = LPSG_DW+(CIC_N*$clog2(CIC_R*CIC_M));

reg                 [4-1:0]                 cnt_div;
wire                                        sym_en;
wire                [PRBS_DW-1:0]           prbs_nxt;
reg                 [PRBS_DW-1:0]           prbs_dat;
wire        signed  [LPSG_DW-1:0]           sym_dat;
wire        signed  [LPSG_DW-1:0]           sym_dc;
wire        signed  [LPSG_DW+1-1:0]         sym_ac_c;
wire        signed  [LPSG_DW-1:0]           sym_ac;
wire        signed  [LPSG_DW+GAIN_DW-1:0]   p_dat;
wire                [CIC_ACC_DW-1:0]        cic_intg_dat;
wire                [CIC_ACC_DW-1:0]        cic_comb_dat;
wire        signed  [LPSG_DW-1:0]           cic_dat;

//################################################################################
// main
//################################################################################

always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        cnt_div <=`U_DLY 4'd0;
    end else if (cke==1'b1) begin
        cnt_div <=`U_DLY cnt_div + 1'd1;
    end else
        ;
end
assign sym_en = (cnt_div==4'd0) ? cke : 1'b0;

assign prbs_nxt = prbs_nxt_f(prbs_dat, PRBS_N, LVL_BW, 1'b0, 1'b1);
always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        prbs_dat <=`U_DLY {PRBS_DW{1'b0}};
    end else if (sym_en==1'b1) begin
        prbs_dat <=`U_DLY prbs_nxt;
    end else
        ;
end

assign sym_dat[LPSG_DW-1-:LVL_BW] = prbs_dat[0+:LVL_BW];
assign sym_dat[0+:LPSG_DW-LVL_BW] = ~({LPSG_DW-LVL_BW{1'b1}}>>1);

s_ema #(     // signed Exponential Moving Average, range [-2^(DW-1):2^(DW-1)-1]
        .DW                             (LPSG_DW                        ),	// data width
        .K_MAX                          (EMA_K_MAX                      ),	// Maximum of cfg_k, K_MAX>=1
        .K_MIN                          (EMA_K_MIN                      ) 	// Minimum of cfg_k
) u_dc ( 
        .rst_n                          (rst_n                          ),
        .clk                            (clk                            ),
        .cke                            (sym_en                         ),

        .cur_vld                        (1'b1                           ),
        .cur_data                       (sym_dat                        ),
        .avg_data                       (sym_dc                         ),

        .cfg_clr                        (1'b0                           ),
        .cfg_ini                        ({LPSG_DW{1'b0}}                ),
        .cfg_k                          (cfg_ema_k                      )	// range [K_MIN:K_MAX], alpha /2^k
);

assign sym_ac_c = sym_dat - sym_dc;

s_sat_tru_reg #(     // range [-(2^(IDW-1))/(2^IFW):(2^(IDW-1)-1)/(2^IFW)]
        .IDW                            (LPSG_DW+1                      ),	// input data width
        .IFW                            (0                              ),	// input fractional width
        .ODW                            (LPSG_DW                        ),	// output data width
        .OFW                            (0                              )	// output fractional width
) u_ac_sat ( 
        .rst_n                          (rst_n                          ),
        .clk                            (clk                            ),
        .cke                            (cke                            ),

        .id                             (sym_ac_c                       ),	// s(IDW, IFW), the MSB is sign
        .od                             (sym_ac                         ),	// s(ODW, OFW), the MSB is sign
        .over                           (                               )
);

cic_intg_n #(
        .IDW                            (LPSG_DW                        ),
        .N                              (CIC_N                          ),
        .RM                             (CIC_R*CIC_M                    )
) u_cic_intg ( 
        .rst_n                          (rst_n                          ),
        .clk                            (clk                            ),
        .cke                            (cke                            ),

        .id                             (sym_ac                         ),
        .od                             (cic_intg_dat                   )
);

cic_comb_n #(
        .DW                             (CIC_ACC_DW                     ),
        .N                              (CIC_N                          ),
        .M                              (CIC_M                          )
) u_cic_comb ( 
        .rst_n                          (rst_n                          ),
        .clk                            (clk                            ),
        .cke                            (cke                            ),

        .id                             (cic_intg_dat                   ),
        .od                             (cic_comb_dat                   )
);
assign cic_dat = cic_comb_dat[CIC_ACC_DW-1-:LPSG_DW];

assign p_dat = cic_dat*$signed({1'b0, cfg_gain});

s_sat_tru_reg #(     // range [-(2^(IDW-1))/(2^IFW):(2^(IDW-1)-1)/(2^IFW)]
        .IDW                            (LPSG_DW+GAIN_DW                ),	// input data width
        .IFW                            (GAIN_FW                        ),	// input fractional width
        .ODW                            (LPSG_DW                        ),	// output data width
        .OFW                            (0                              )	// output fractional width
) u_lpsg_dat ( 
        .rst_n                          (rst_n                          ),
        .clk                            (clk                            ),
        .cke                            (cke                            ),

        .id                             (p_dat                          ),	// s(IDW, IFW), the MSB is sign
        .od                             (lpsg_dat                       ),	// s(ODW, OFW), the MSB is sign
        .over                           (                               )
);

`include "func_prbs.v"  // refer to prbs_nxt_f()
`include "func_param.v" // refer to MAX_F()

//################################################################################
// ASSERTION
//################################################################################

`ifdef CBB_ASSERT_ON
// synopsys translate_off


// synopsys translate_on
`endif

endmodule
